#!/usr/bin/env python
# coding: utf-8

# # 动静态图

# 目前主流的深度学习框架有静态图(Graph)和动态图(PyNative)两种执行模式。
# 
# - 静态图模式下，程序在编译执行时，首先生成神经网络的图结构，然后再执行图中涉及的计算操作。因此，在静态图模式下，编译器可以通过使用图优化等技术来获得更好的执行性能，有助于规模部署和跨平台运行。
# 
# - 动态图模式下，程序按照代码的编写顺序逐行执行，在执行正向过程中根据反向传播的原理，动态生成反向执行图。这种模式下，编译器将神经网络中的各个算子逐一下发到设备进行计算操作，方便用户编写和调试神经网络模型。
# 
# ## 动静态图介绍
# 
# luojianet_ms提供了静态图和动态图统一的编码方式，大大增加了静态图和动态图的可兼容性，用户无需开发多套代码，仅变更一行代码便可切换静态图/动态图模式。静态图模式是luojianet_ms的默认模式，而动态图模式用于调试等用途。
# 
# ### 模式选择
# 
# 通过配置context参数可以控制程序运行的模式，动态图和静态图两种模式的区别主要有：
# 
# - **适用场景**：静态图需要一开始就构建好网络结构，然后框架做整图优化和执行，比较适合网络固定没有变化，且需要高性能的场景。动态图逐行执行算子，支持执行单算子、普通函数和网络，以及单独求梯度的操作。
# 
# - **网络执行**：静态图模式和动态图模式在执行相同的网络和算子时，精度效果一致。由于静态图模式运用了图优化、计算图整图下沉等技术，静态图模式执行网络的性能和效率更高，动态图模式更便于调试调优。
# 
# - **代码调试**：在脚本开发和网络流程调试中，推荐使用动态图模式进行调试。在动态图模式下，可以方便地设置断点，获取网络执行的中间结果，也可以通过pdb的方式对网络进行调试。而静态图模式无法设置断点，只能先指定算子进行打印，然后在网络执行完成后查看输出结果。
# 
# ### 模式切换
# 
# 模式切换时，需要设置context中的运行模式。首先定义网络模型`MyNet`和后续代码片段用到的数据，用于后续的动静态图模式的切换和展示：

# In[1中低阶API实现深度学习]:


import numpy as np
import luojianet_ms.nn as nn
import luojianet_ms.ops as ops
from luojianet_ms import Tensor, Parameter
from luojianet_ms import dtype as mstype

class MyNet(nn.Module):
    """自定义网络，实现两个张量的加法"""
    def __init__(self):
        super(MyNet, self).__init__()
        self.add = ops.Add()

    def forward(self, x, y):
        return self.add(x, y)

x = Tensor(np.array([1.0, 2.0, 3.0]).astype(np.float32))
y = Tensor(np.array([4.0, 5.0, 6.0]).astype(np.float32))


# 设置运行模式为静态图模式：

# In[2高级数据集管理]:


from luojianet_ms import context

context.set_context(mode=context.GRAPH_MODE)

net = MyNet()
print(net(x, y))


# luojianet_ms处于静态图模式时，可以通过`mode=context.PYNATIVE_MODE`切换为动态图模式；同样，luojianet_ms处于动态图模式时，可以通过`mode=context.GRAPH_MODE`切换为静态图模式，请留意[静态图语法支持](https://www.luojianet_ms.cn/docs/zh-CN/r1.7/note/static_graph_syntax_support.html)。

# In[3图像处理]:


context.set_context(mode=context.PYNATIVE_MODE)

net = MyNet()
print(net(x, y))


# ## 静态图
# 
# 在luojianet_ms中，静态图模式又被称为Graph模式，比较适合网络固定且需要高性能的场景，可以通过`context.set_context`接口中，参数`mode`入参为`context.GRAPH_MODE`来设置成静态图模式。
# 
# 在静态图模式下，基于图优化、计算图整图下沉等技术，编译器可以针对图进行全局的优化，因此在静态图模式下执行时可以获得较好的性能。但是，执行图是从源码转换而来，因此在静态图模式下不是所有的Python语法都能支持，会有一些特殊的约束.
# 
# ### 静态图模式执行原理
# 
# 在静态图模式下，luojianet_ms通过源码转换的方式，将Python的源码转换成中间表达形式，也就是IR（Intermediate Representation），并在此基础上对IR图进行优化，最终在硬件设备上执行优化后的图。
# 
# luojianet_ms使用的是一种基于图表示的函数式IR，称为MindIR。静态图模式就是基于MindIR进行编译优化，使用静态图模式时，需要使用[nn.Module]类并且在`forward`函数中编写执行代码。
# 
# ### 静态图模式代码示例
# 
# 静态图模式的代码用例如下所示，神经网络模型实现 $f(x, y)=x*y$ 的计算操作：

# In[4自然语言]:


# 设置运行模式为静态图模式
context.set_context(mode=context.GRAPH_MODE)

class Net(nn.Module):
    """自定义网络，实现两个张量的乘法"""
    def __init__(self):
        super(Net, self).__init__()
        self.mul = ops.Mul()

    def forward(self, x, y):
        """定义执行代码"""
        return self.mul(x, y)

x = Tensor(np.array([1.0, 2.0, 3.0]).astype(np.float32))
y = Tensor(np.array([4.0, 5.0, 6.0]).astype(np.float32))

net = Net()

print(net(x, y))


# ### 静态图模式下的控制流
# 
# ## 动态图
# 
# 在luojianet_ms中，动态图模式又被称为PyNative模式，可以通过`context.set_context`接口中，参数`mode`入参为`context.PYNATIVE_MODE`来设置成动态图模式。
# 
# 在脚本开发和网络流程调试中，推荐使用动态图模式进行调试，其支持执行单算子、普通函数和网络、以及单独求梯度的操作。
# 
# ### 动态图模式执行原理
# 
# 在动态图模式下，用户可以使用完整的Python API，此外针对使用luojianet_ms提供的API时，框架会根据用户选择的不同硬件平台（Ascend/GPU/CPU）或环境信息，将算子API的操作在对应的硬件平台上执行，并返回相应的结果。
# 
# 框架整体的执行过程如下：
# 
# 通过前端的Python API，调用到框架层，最终到相应的硬件设备上进行计算。
# 
# 下面我们通过`ops.mul`算子，直接代替静态图模式下需要定义网络模型，实现 $f(x, y)=x*y$ 的计算操作：

# In[5]:


# 设置运行模式为动态图模式
context.set_context(mode=context.PYNATIVE_MODE)

x = Tensor(np.array([1.0, 2.0, 3.0]).astype(np.float32))
y = Tensor(np.array([4.0, 5.0, 6.0]).astype(np.float32))

output = ops.mul(x, y)

print(output.asnumpy())


# 在上面的示例代码中，当调用到API接口`ops.mul(x, y)`时，会将luojianet_ms表达层Python API接口的调用，通过[Pybind11](https://pybind11.readthedocs.io/en/stable/basics.html)调用到luojianet_ms框架的C++层，转换成C++的接口调用。接着框架会根据luojianet_ms的安装环境信息，自动选择对应的硬件设备，在该硬件设备上执行add操作。
# 
# 从上述原理可以看到，PyNative模式下，Python脚本代码会根据Python的语法进行执行，而执行过程中涉及到luojianet_ms表达层的Python API，会根据用户的设置在不同的硬件上执行，从而进行性能加速。
# 
# 因此，在动态图模式下，用户可以随意使用Python的语法以及调试方法。
# 
# ### 动态图模式自动微分原理
# 
# 在动态图下，执行正向过程完全是按照Python的语法执行的，而反向传播过程是基于Tensor实现的。
# 
# 因此，我们在执行正向过程中，将所有应用于Tensor的操作记录下来，并针对每个计算操作求取其反向，然后将所有反向过程串联起来形成整体反向传播图，最终将反向图在设备上执行并计算出梯度。
# 
# 下面通过一段简单的示例代码说明动态图模式自动微分原理。对矩阵x乘上固定参数z，然后与y进行矩阵乘法：
# 
# $$f(x, y)=(x * z) * y \tag{1中低阶API实现深度学习}$$
# 
# 代码如下：

# In[6]:


# 设置运行模式为动态图模式
context.set_context(mode=context.PYNATIVE_MODE)

class Net(nn.Module):
    """自定义网络"""
    def __init__(self):
        super(Net, self).__init__()
        self.matmul = ops.MatMul()
        self.z = Parameter(Tensor(np.array([1.0], np.float32)), name='z')

    def forward(self, x, y):
        x = x * self.z
        x = self.matmul(x, y)
        return x

class GradNetWrtX(nn.Module):
    """定义对x的求导"""
    def __init__(self, net):
        super(GradNetWrtX, self).__init__()

        self.net = net
        self.grad_op = ops.GradOperation()

    def forward(self, x, y):
        gradient_function = self.grad_op(self.net)
        return gradient_function(x, y)

x = Tensor([[0.8, 0.6, 0.2], [1.8, 1.3, 1.1]], dtype=mstype.float32)
y = Tensor([[0.11, 3.3, 1.1], [1.1, 0.2, 1.4], [1.1, 2.2, 0.3]], dtype=mstype.float32)

output = GradNetWrtX(Net())(x, y)
print(output)


# > 由于不同计算平台的精度可能存在差异，因此上面的代码在不同平台上的执行结果会存在微小的差别。
# 
# ![forward](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r1.7/tutorials/source_zh_cn/advanced/pynative_graph/images/forward_backward.png)
# 
# 根据上述动态图模式下构图原理可以看到，在正向传播过程中，luojianet_ms记录了Mul的计算过程，根据Mul对应的反向bprop的定义，得到了反向的MulGrad算子。
# 
# 根据Mul算子的bprop定义，如下：

# In[7]:


from luojianet_ms.ops._grad.grad_base import bprop_getters

bprop_getters.register(ops.Mul)
def get_bprop_mul(self):
    """Grad definition for `Mul` operation."""
    mul_func = ops.Mul()

    def bprop(x, y, out, dout):
        bc_dx = mul_func(y, dout)
        bc_dy = mul_func(x, dout)
        return bprop(x, y, bc_dx, bc_dy)

    return bprop


# 可以看到对Mul的输入求反向，需要两个输入和输出的反向传播梯度值，此时根据实际的输入值，可以将z连接到MulGrad。以此类推，对下一个算子Matmul，相应的得到MatmulGrad信息，再根据bprop的输入输出，将上下文梯度传播连接起来。
# 
# 同理对于输入y求导，可以使用同样的过程进行推导。
# 
# ### 动态图模式下的控制流
# 
# 在luojianet_ms中，针对控制流语法并没有做特殊处理，直接按照Python的语法展开执行，进而对展开的执行算子进行自动微分操作。
# 
# 例如，对于for循环，在动态图下会首先执行Python的源码，然后根据具体的循环次数，不断的执行for循环中的语句，并对其算子进行自动微分操作。

# In[8]:


# 设置运行模式为动态图模式
context.set_context(mode=context.PYNATIVE_MODE)

class Net(nn.Module):
    """自定义网络"""
    def __init__(self):
        super(Net, self).__init__()
        self.matmul = ops.MatMul()
        self.z = Parameter(Tensor(np.array([1.0], np.float32)), name='z')

    def forward(self, x):
        for _ in range(3):
            x = x + self.z
        return x

x = Tensor(np.array([1.0, 2.0, 3.0]).astype(np.float32))
net = Net()
output = net(x)

print(output)

